/* RIPngd Zebra
 * Copyright (C) 2002 6WIND <vincent.jardin@6wind.com>
 *
 * This file is part of GNU Zebra.
 *
 * GNU Zebra is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * GNU Zebra is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNU Zebra; see the file COPYING.  If not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.  
 */

/* This file is required in order to support properly the RIPng nexthop
 * feature.
 */

#include <zebra.h>

/* For struct udphdr. */
#include <netinet/udp.h>

#include "linklist.h"
#include "stream.h"
#include "log.h"
#include "memory.h"
#include "vty.h"
#include "if.h"
#include "prefix.h"

#include "ripngd/ripngd.h"
#include "ripngd/ripng_debug.h"
#include "ripngd/ripng_nexthop.h"

#define DEBUG 1

#define min(a, b) ((a) < (b) ? (a) : (b))

struct ripng_rte_data {
  struct prefix_ipv6 *p;
  struct ripng_info *rinfo;
  struct ripng_aggregate *aggregate;
};

void _ripng_rte_del(struct ripng_rte_data *A);
int _ripng_rte_cmp(struct ripng_rte_data *A, struct ripng_rte_data *B);

#define METRIC_OUT(a) \
    ((a)->rinfo ?  (a)->rinfo->metric_out : (a)->aggregate->metric_out)
#define NEXTHOP_OUT_PTR(a) \
    ((a)->rinfo ?  &((a)->rinfo->nexthop_out) : &((a)->aggregate->nexthop_out))
#define TAG_OUT(a) \
    ((a)->rinfo ?  (a)->rinfo->tag_out : (a)->aggregate->tag_out)

struct list *
ripng_rte_new(void) {
  struct list *rte;

  rte = list_new();
  rte->cmp = (int (*)(void *, void *)) _ripng_rte_cmp;
  rte->del = (void (*)(void *)) _ripng_rte_del;

  return rte;
}

void
ripng_rte_free(struct list *ripng_rte_list) {
  list_delete(ripng_rte_list);
}

/* Delete RTE */
void
_ripng_rte_del(struct ripng_rte_data *A) {
  XFREE(MTYPE_RIPNG_RTE_DATA, A);
}

/* Compare RTE:
 *  return +  if A > B
 *         0  if A = B
 *         -  if A < B
 */
int
_ripng_rte_cmp(struct ripng_rte_data *A, struct ripng_rte_data *B) {
  return addr6_cmp(NEXTHOP_OUT_PTR(A), NEXTHOP_OUT_PTR(B));
}

/* Add routing table entry */
void
ripng_rte_add(struct list *ripng_rte_list, struct prefix_ipv6 *p,
              struct ripng_info *rinfo, struct ripng_aggregate *aggregate) {

  struct ripng_rte_data *data;

  /* At least one should not be null */
  assert(!rinfo || !aggregate);

  data = XMALLOC(MTYPE_RIPNG_RTE_DATA, sizeof(*data));
  data->p     = p;
  data->rinfo = rinfo;
  data->aggregate = aggregate;

  listnode_add_sort(ripng_rte_list, data);
} 

/* Send the RTE with the nexthop support
 */
void
ripng_rte_send(struct list *ripng_rte_list, struct interface *ifp,
               struct sockaddr_in6 *to) {

  struct ripng_rte_data *data;
  struct listnode *node, *nnode;

  struct in6_addr last_nexthop;
  struct in6_addr myself_nexthop;

  struct stream *s;
  int num;
  int mtu;
  int rtemax;
  int ret;

  /* Most of the time, there is no nexthop */
  memset(&last_nexthop, 0, sizeof(last_nexthop));

  /* Use myself_nexthop if the nexthop is not a link-local address, because
   * we remain a right path without beeing the optimal one.
   */
  memset(&myself_nexthop, 0, sizeof(myself_nexthop));

  /* Output stream get from ripng structre.  XXX this should be
     interface structure. */
  s = ripng->obuf;

  /* Reset stream and RTE counter. */
  stream_reset (s);
  num = 0;

  mtu = ifp->mtu6;
  if (mtu < 0)
    mtu = IFMINMTU;

  rtemax = (min (mtu, RIPNG_MAX_PACKET_SIZE) -
	    IPV6_HDRLEN - 
	    sizeof (struct udphdr) -
	    sizeof (struct ripng_packet) +
	    sizeof (struct rte)) / sizeof (struct rte);

  for (ALL_LIST_ELEMENTS (ripng_rte_list, node, nnode, data)) {
    /* (2.1) Next hop support */
    if (!IPV6_ADDR_SAME(&last_nexthop, NEXTHOP_OUT_PTR(data))) {

      /* A nexthop entry should be at least followed by 1 RTE */
      if (num == (rtemax-1)) {
	ret = ripng_send_packet ((caddr_t) STREAM_DATA (s), stream_get_endp (s),
				 to, ifp);

        if (ret >= 0 && IS_RIPNG_DEBUG_SEND)
          ripng_packet_dump((struct ripng_packet *)STREAM_DATA (s),
			    stream_get_endp(s), "SEND");
        num = 0;
        stream_reset (s);
      }

      /* Add the nexthop (2.1) */

      /* If the received next hop address is not a link-local address,
       * it should be treated as 0:0:0:0:0:0:0:0.
       */
      if (!IN6_IS_ADDR_LINKLOCAL(NEXTHOP_OUT_PTR(data)))
        last_nexthop = myself_nexthop;
      else
	last_nexthop = *NEXTHOP_OUT_PTR(data);

      num = ripng_write_rte(num, s, NULL, &last_nexthop, 0, RIPNG_METRIC_NEXTHOP);
    } else {
      /* Rewrite the nexthop for each new packet */
      if ((num == 0) && !IPV6_ADDR_SAME(&last_nexthop, &myself_nexthop))
        num = ripng_write_rte(num, s, NULL, &last_nexthop, 0, RIPNG_METRIC_NEXTHOP);
    }
    num = ripng_write_rte(num, s, data->p, NULL,
			  TAG_OUT(data), METRIC_OUT(data));

    if (num == rtemax) {
      ret = ripng_send_packet ((caddr_t) STREAM_DATA (s), stream_get_endp (s),
			       to, ifp);

      if (ret >= 0 && IS_RIPNG_DEBUG_SEND)
        ripng_packet_dump((struct ripng_packet *)STREAM_DATA (s),
			  stream_get_endp(s), "SEND");
      num = 0;
      stream_reset (s);
    }
  }

  /* If unwritten RTE exist, flush it. */
  if (num != 0) {
    ret = ripng_send_packet ((caddr_t) STREAM_DATA (s), stream_get_endp (s),
			     to, ifp);

    if (ret >= 0 && IS_RIPNG_DEBUG_SEND)
      ripng_packet_dump ((struct ripng_packet *)STREAM_DATA (s),
			 stream_get_endp (s), "SEND");
    num = 0;
    stream_reset (s);
  }
}
